(function () {
    "use strict";

    require("./assign");
    var React = require("react");
    var EventBus = require("./event-bus");

    var resizeBorderWidth = 15;

    var mouseMode = {
        resizeTopLeft: 0,
        resizeTop: 1,
        resizeTopRight: 2,
        resizeRight: 3,
        resizeBottomRight: 4,
        resizeBottom: 5,
        resizeBottomLeft: 6,
        resizeLeft: 7,
        move: 8
    };

    function getMouseMode(e, rect) {
        var top = e.clientY - rect.top < resizeBorderWidth;
        var right = rect.left + rect.width - e.clientX < resizeBorderWidth;
        var left = e.clientX - rect.left < resizeBorderWidth;
        var bottom = rect.top + rect.height - e.clientY < resizeBorderWidth;
        if (left && top) {
            return mouseMode.resizeTopLeft;
        } else if (right && top) {
            return mouseMode.resizeTopRight;
        } else if (top) {
            return mouseMode.resizeTop;
        } else if (right && bottom) {
            return mouseMode.resizeBottomRight;
        } else if (right) {
            return mouseMode.resizeRight;
        } else if (left && bottom) {
            return mouseMode.resizeBottomLeft;
        } else if (bottom) {
            return mouseMode.resizeBottom;
        } else if (left) {
            return mouseMode.resizeLeft;
        } else {
            return mouseMode.move;
        }
    }

    function getMouseOffset(e, rect) {
        return {
            x: e.clientX - rect.left,
            y: e.clientY - rect.top
        };
    }

    function preventDefault(e) {
        e.preventDefault();
    }

    function stopPropagation(e) {
        e.stopPropagation();
    }

    function percent(value) {
        return String(value * 100) + "%";
    }

    function rational(pixels, fullSpan) {
        if (fullSpan === 0) {
            return 0;
        }

        return pixels / fullSpan;
    }

    function topZIndex(blocks) {
        return Math.max.apply(Math, blocks.map(function (block) {
            return block.zIndex;
        }));
    }

    function adjustedSelectorBlockSize(value) {
        return percent(value * 3);
    }

    var windowEvents = new EventBus;
    window.onmouseup = windowEvents.trigger.bind(windowEvents, "mouseup");
    window.onmousemove = windowEvents.trigger.bind(windowEvents, "mousemove");
    window.ondrop = windowEvents.trigger.bind(windowEvents, "drop");

    var Property = React.createClass({
        render: function () {
            return (
                <div className="document-properties">
                    <h3>Properties</h3>
                </div>
            );
        }
    });

    var SelectorLayout = React.createClass({
        getDefaultProps: function () {
            return {
                blocks: [],
                selected: false,
                onSelect: function () {},
                onDragStart: function () {}
            };
        },

        render: function () {
            return (
                <div
                    draggable={true}
                    className={
                        "layout" +
                        (this.props.selected ? " selected" : "")
                    }
                    onClick={this.props.onSelect}
                    onDragStart={this.props.onDragStart}>
                    {this.props.blocks.map(function (block) {
                        return (
                            <div
                                key={block.id}
                                className="block"
                                style={{
                                    left: percent(block.position.x),
                                    top: percent(block.position.y),
                                    width: adjustedSelectorBlockSize(block.size.width),
                                    height: adjustedSelectorBlockSize(block.size.height),
                                    zIndex: block.zIndex
                                }}>
                                {block.text}
                            </div>
                        );
                    }, this)}
                </div>
            );
        }
    });

    var Selector = React.createClass({
        getDefaultProps: function () {
            return {
                items: [],
                selectedIndex: -1,
                onSelect: function () {},
                onRequestNew: function () {},
                onPosition: function () {}
            };
        },

        handleRequestNewFocus: function () {
            this.refs["request-new"].getDOMNode().blur();
        },

        handleDragStart: function (index, e) {
            var clientRect = e.target.getBoundingClientRect();
            var offset = e.clientY - clientRect.top;
            e.dataTransfer.setData("text/plain", "layout:" + index + ":" + offset);
        },

        handleDrop: function (e) {
            var data = e.dataTransfer.getData("text/plain");
            if (!data) {
                return;
            }

            var m = data.match(/^layout:([0-9]+):([0-9]+([.][0-9]+)?)$/);
            if (!m) {
                return;
            }

            var oldIndex = parseInt(m[1], 10);

            var offset = parseInt(m[2], 10);
            var clientY = e.clientY - offset;

            var newIndex = -1;
            var layouts = this.getDOMNode().getElementsByClassName("layout");
            for (var index = 0; index < layouts.length; index++) {
                var layout = layouts[index];
                var clientRect = layout.getBoundingClientRect();
                if (index < oldIndex && clientRect.top > clientY) {
                    newIndex = index;
                    break;
                }

                if (index > oldIndex && clientRect.top < clientY) {
                    newIndex = index;
                }
            }

            if (newIndex < 0) {
                return;
            }

            this.props.onPosition({
                oldIndex: oldIndex,
                newIndex: newIndex
            });
        },

        render: function () {
            return (
                <div className={this.props.className}
                    onDragOver={preventDefault}
                    onDrop={this.handleDrop}>
                    {this.props.items
                        .map(function (item, index) {
                            return (
                                <SelectorLayout
                                    key={item.id}
                                    blocks={item.blocks}
                                    selected={index === this.props.selectedIndex}
                                    onSelect={this.props.onSelect.bind(this, {index: index})}
                                    onDragStart={this.handleDragStart.bind(this, index)}
                                    />
                            );
                        }, this)}
                    <button
                        className="request-new"
                        ref="request-new"
                        onClick={this.props.onRequestNew}
                        onFocus={this.handleRequestNewFocus}>
                        +
                    </button>
                </div>
            );
        }
    });

    var EditorBlock = React.createClass({
        getDefaultProps: function () {
            return {
                block: {
                    id: -1,
                    position: {
                        x: 0,
                        y: 0
                    },
                    size: {
                        width: 0,
                        height: 0
                    },
                    zIndex: 0
                },

                onMove: function () {},
                onResize: function () {},
                onTyping: function () {}
            };
        },

        initMove: function (e) {
            if (e.button !== 0) {
                return;
            }

            var node = this.getDOMNode();
            var nodeRect = node.getBoundingClientRect();
            var mode = getMouseMode(e, nodeRect);
            var offset = getMouseOffset(e, nodeRect);

            var moving = {
                block: this.props.block,
                offset: offset,
                onMove: this.props.onMove,
                onResize: this.props.onResize,

                createMoveArgs: function (e, moving) {
                    return {
                        block: this.block,
                        mode: mode,
                        node: node,
                        clientX: e.clientX - this.offset.x,
                        clientY: e.clientY - this.offset.y,
                        moving: moving
                    };
                },

                trigger: function (e, moving) {
                    (mode === mouseMode.move ?
                        this.onMove : this.onResize)(this.createMoveArgs(e, moving));
                }
            };

            moving.onMouseUp = function (e) {
                windowEvents.off("mouseup", this.onMouseUp);
                windowEvents.off("mousemove", this.onMouseMove);
                node.classList.remove("grabbing");
                this.trigger(e, false);
            }.bind(moving);

            moving.onMouseMove = function (e) {
                this.trigger(e, true);
            }.bind(moving);

            moving.onDrop = function (e) {
                windowEvents.off("mouseup", this.onMouseUp);
                windowEvents.off("mousemove", this.onMouseMove);
            }.bind(moving);

            windowEvents.on("mouseup", moving.onMouseUp);
            windowEvents.on("mousemove", moving.onMouseMove);
            windowEvents.on("drop", moving.onDrop);

            node.classList.add("grabbing");
        },

        handleTextMouseDown: function (e) {
            e.stopPropagation();

            var text = this.getDOMNode().getElementsByClassName("text")[0];
            if (!this.props.block.text && text.innerText.trim() === "[edit text]") {
                text.innerText = "";
            }
        },

        handleTyping: function () {
            if (this.typing) {
                return;
            }

            this.typing = true;
            setTimeout(function () {
                try {
                    var text = this.getDOMNode().getElementsByClassName("text")[0];
                    this.props.onTyping({content: text.innerText});
                    this.typing = false;
                    this.handleTyping();
                } catch (error) {
                    this.typing = false;
                }
            }.bind(this), 120);
        },

        render: function () {
            return (
                <div
                    className="block"
                    style={{
                        left: percent(this.props.block.position.x),
                        top: percent(this.props.block.position.y),
                        width: percent(this.props.block.size.width),
                        height: percent(this.props.block.size.height),
                        zIndex: this.props.block.zIndex
                    }}
                    onMouseDown={this.initMove}>
                    <span
                        className="text"
                        contentEditable={true}
                        onMouseDown={this.handleTextMouseDown}
                        onKeyDown={this.handleTyping}>
                        {this.props.block.text}
                    </span>
                </div>
            );
        }
    });

    var Layout = React.createClass({
        getDefaultProps: function () {
            return {
                visible: false,
                isTemplate: false,
                onDelete: function () {},
                onNewBlock: function () {},
                onBlockMove: function () {},
                onBlockResize: function () {},
                onTyping: function () {},
                onSetDocumentTemplate: function () {},
                blocks: []
            };
        },

        getEditorArea: function () {
            return this.getDOMNode()
                .getElementsByClassName("editor-area")[0];
        },

        getEditorRect: function () {
            return this.getEditorArea().getBoundingClientRect();
        },

        handleDrop: function (e) {
            var data = e.dataTransfer.getData("text/plain");
            if (!data) {
                return;
            }

            var m = data.match(/^block-asset:(.+)$/)
            if (!m) {
                return;
            }

            var editorRect = this.getEditorRect();

            this.props.onNewBlock({
                type: m[1],
                x: rational(e.clientX - editorRect.left, editorRect.width),
                y: rational(e.clientY - editorRect.top, editorRect.height)
            });
        },

        handleBlockMove: function (e) {
            var editorRect = this.getEditorRect();
            var x = rational(e.clientX - editorRect.left, editorRect.width);
            var y = rational(e.clientY - editorRect.top, editorRect.height);
            e.node.style.left = percent(x);
            e.node.style.top = percent(y);

            var zIndex = topZIndex(this.props.blocks);
            if (e.block.zIndex !== zIndex) {
                e.node.style.zIndex = zIndex + 1;
            }

            this.props.onBlockMove({
                blockId: e.block.id,
                x: x,
                y: y,
                moving: e.moving
            });
        },

        handleBlockResize: function (e) {
            var editorRect = this.getEditorRect();
            var dx = rational(e.clientX - editorRect.left, editorRect.width) - e.block.position.x;
            var dy = rational(e.clientY - editorRect.top, editorRect.height) - e.block.position.y;

            var x = e.block.position.x;
            var y = e.block.position.y;
            var width = e.block.size.width;
            var height = e.block.size.height;

            switch (e.mode) {
            case mouseMode.resizeTopLeft:
                x += dx;
                y += dy;
                width -= dx;
                height -= dy;
                break;
            case mouseMode.resizeTop:
                y += dy;
                height -= dy;
                break;
            case mouseMode.resizeTopRight:
                y += dy;
                width += dx;
                height -= dy;
                break;
            case mouseMode.resizeRight:
                width += dx;
                break;
            case mouseMode.resizeBottomRight:
                width += dx;
                height += dy;
                break;
            case mouseMode.resizeBottom:
                height += dy;
                break;
            case mouseMode.resizeBottomLeft:
                x += dx;
                width -= dx;
                height += dy;
                break;
            case mouseMode.resizeLeft:
                x += dx;
                width -= dx;
                break;
            }

            e.node.style.left = percent(x);
            e.node.style.top = percent(y);
            e.node.style.width = percent(width);
            e.node.style.height = percent(height);

            this.props.onBlockResize({
                blockId: e.block.id,
                x: x,
                y: y,
                width: width,
                height: height,
                moving: e.moving
            });
        },

        handleTyping: function (id, e) {
            this.props.onTyping({
                blockId: id,
                content: e.content
            });
        },

        render: function () {
            return (
                <div className={"layout-editor" +
                    (this.props.visible ? "" : " hidden")}
                    onDragOver={preventDefault}
                    onDrop={this.handleDrop}>
                    <div className="editor-area">
                        {this.props.blocks.map(function (block) {
                            return <EditorBlock
                                key={block.id}
                                block={block}
                                onMove={this.handleBlockMove}
                                onResize={this.handleBlockResize}
                                onTyping={this.handleTyping.bind(this, block.id)}
                                />
                        }, this)}
                    </div>
                    <div className="layout-controls">
                        <button
                            className={"set-document-template" +
                                (this.props.isTemplate ? "" : " hidden")}
                            onClick={this.props.onSetDocumentTemplate}>
                            Set As Document Template
                        </button>
                        <button
                            className="delete-layout"
                            onClick={this.props.onDelete}>
                            Delete
                        </button>
                    </div>
                </div>
            );
        }
    });

    var BlockAsset = React.createClass({
        getDefaultProps: function () {
            return {
                type: "",
                label: ""
            };
        },

        handleDragStart: function (e) {
            e.dataTransfer.setData("text/plain", "block-asset:" + this.props.type);
        },

        render: function () {
            return (
                <div
                    className="block-type"
                    draggable={true}
                    onDragStart={this.handleDragStart}>
                    {this.props.label}
                </div>
            );
        }
    });

    var Document = React.createClass({
        getDefaultProps: function () {
            return {
                tmeplates: [],
                pages: [],
                layout: null
            };
        },

        render: function () {
            return (
                <div>
                    <div className="document-controls">
                        <Property ref="property" />
                        <div className="block-selector">
                            <BlockAsset type="text" label="Text" />
                            <BlockAsset type="image" label="Image" />
                        </div>
                        <Selector
                            ref="template-selector"
                            className="template-selector"
                            {...this.props["template-selector"]}
                            />
                        <div className="image-selector" />
                    </div>
                    <Selector
                        ref="page-selector"
                        className="page-selector"
                        {...this.props["page-selector"]}
                        />
                    <Layout
                        ref="layout-editor"
                        {...this.props["layout-editor"]}
                        />
                </div>
            );
        }
    });

    Object.assign(module.exports, {
        Property: Property,
        SelectorLayout: SelectorLayout,
        Selector: Selector,
        EditorBlock: EditorBlock,
        Layout: Layout,
        BlockAsset: BlockAsset,
        Document: Document,
        createDocument: function (root, props) {
            return React.render(<Document {...(props || {})} />, root);
        },
        windowEvents: windowEvents,
        adjustedSelectorBlockSize: adjustedSelectorBlockSize
    });
})();
